﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PubSubLite.Configuration;
using System.Threading;
using PubSubLite.Messaging;
using PubSubLite.Utils;
using PubSubLite.Messaging.MessageChannels;

namespace PubSubLite
{
    public class Bus : IBus
    {
        public Bus()
        {
            _subscribers = SubscribersStorage.Default;
            _configuration = new BusConfiguration();
            _helper = new BusHelper(this, _configuration);
            var channel = new InMemoryMessageChannel();
            _messageDispatcher = new MessageDispatcher(_configuration, channel);
            _commandDispatcher = new CommandDispatcher(_configuration, channel);
            _channelProvider = new MessageChannelProvider(_configuration);
            _messageListener = new MessageChannelListener(_channelProvider, _commandDispatcher, _messageDispatcher);
            _messageRouter = new MessageRouter(_channelProvider, _configuration);
            _messageListener.Start();
        }

        public Bus(
            ISubscribersStorage subscribers, 
            IMessageChannelListener listener,
            IMessageDispatcher messageDispatcher,
            ICommandDispatcher commandDispatcher,
            IBusConfiguration configuration,
            IMessageRouter messageRouter,
            IMessageChannelProvider channelProvider
            )
        {
            // Guarding
            Require.Argument.IsNotNull(subscribers, "subscribers");
            Require.Argument.IsNotNull(listener, "listener");
            Require.Argument.IsNotNull(messageDispatcher, "messageDispatcher");
            Require.Argument.IsNotNull(commandDispatcher, "commandDispatcher");
            Require.Argument.IsNotNull(configuration, "configuration");
            Require.Argument.IsNotNull(channelProvider, "channelProvider");
            Require.Argument.IsNotNull(messageRouter, "messagreRouter");

            _subscribers = subscribers;
            _messageListener = listener;
            _messageDispatcher = messageDispatcher;
            _commandDispatcher = commandDispatcher;
            _messageRouter = messageRouter;
            _channelProvider = channelProvider;
            _configuration = configuration;
            _helper = new BusHelper(this, _configuration);
            _messageListener.Start();
            ((IBus)this).Configure();
        }

        private ISubscribersStorage _subscribers;
        private IMessageDispatcher _messageDispatcher;
        private ICommandDispatcher _commandDispatcher;
        private IMessageChannelListener _messageListener;
        private IMessageChannelProvider _channelProvider;
        private IMessageRouter _messageRouter;
        private IBusConfiguration _configuration;
        private BusHelper _helper;


        IBusConfigurationSetup IBus.Configure(Func<IBusConfiguration, IBusConfigurationSetup> config)
        {
            Require.Argument.IsNotNull(config, "config");
            config(_configuration);
            return _configuration;
        }

        IBusConfigurationSetup IBus.Configure()
        {
            return _configuration;
        }

        IEnumerable<ISubscription<T>> SubscribeAllHandler<T>(Func<IEnumerable<ISubscriber<T>>> subscriptions)
        {
            Require.Argument.IsNotNull(subscriptions, "subscriptions");
            var result = new List<ISubscription<T>>();
            var sList = subscriptions().ToList();
            sList.ForEach(x =>
                {
                    result.Add(((IBus)this).Subscribe<T>(x));
                });
            return result;
        }

        IEnumerable<ISubscription<T>> IBus.SubscribeAll<T>(Func<IEnumerable<ISubscriber<T>>> subscriptions)
        {
            return _helper.Execute<Func<IEnumerable<ISubscriber<T>>>, IEnumerable<ISubscription<T>>>(s => SubscribeAllHandler<T>(s), subscriptions);
        }

        private ISubscription<T> SubscribeHandler<T>(ISubscriber<T> subscriber)
        {
            Require.Argument.IsNotNull(subscriber, "subscriber");
            var subscription = new Subscription<T>(subscriber);
            _subscribers.AddSubscriber<T>(subscription);
            //_messageRouter.RouteInternalMessage<Subscribe<T>>(new InternalMessage<Subscribe<T>>(new Subscribe<T>(subscriber)));
            return subscription;
        }

        ISubscription<T> IBus.Subscribe<T>(ISubscriber<T> subscriber)
        {
            return _helper.Execute<ISubscriber<T>, ISubscription<T>>(s => SubscribeHandler(s), subscriber);
        }

        void IBus.Unsubscribe(Guid subscriptionID)
        {
            _subscribers.RemoveSubscription(subscriptionID);
        }

        private void PublishHandler<T>(T data, string category)
            where T : class
        {
            _messageRouter.RouteMessage<T>(new Message<T>(data, category));
        }


        void IBus.Publish<T>(T data)
        {
            _helper.Execute<T>(d => PublishHandler(d, string.Empty), data);
        }

        void IBus.Publish<T>(T data, string category)
        {
            _helper.Execute<T>(d => PublishHandler(d, category), data);
        }

        private void PublishCommandHandler<T, T2>(T data, Action<T2> callback)
            where T : class
        {
            var message = new CommandMessage<T, T2>(data, callback);
            _messageRouter.RouteCommand<T, T2>(message);
        }

        void IBus.PublishCommand<T, TReturnMessageType>(T data, Action<TReturnMessageType> callback)
        {
            _helper.Execute<T>(d => PublishCommandHandler<T, TReturnMessageType>(d, callback), data);
        }

        private void PublishCommandReplyHandler<T>(T data, Guid correlatingMessageID)
            where T : class
        {
            var message = new Message<T>(data, correlatingMessageID);
            this._messageRouter.RouteMessage<T>(message);
        }

        void IBus.PublishCommandReply<T>(T data, Guid correlatingMessageID)
        {
            _helper.Execute(d => PublishCommandReplyHandler<T>(d, correlatingMessageID), data);
        }

        void IBus.ClearSubscribers()
        {
            _subscribers.Clear();
        }
    }
}
